#pragma once
#include <functional>
#include <map>
#include <memory>
#include <nlohmann/json.hpp>
#include <string>

template <class T> class Factory
{
  public:
    template <class N> struct Register
    {
        Register(const std::string &name)
        {
            Factory::Instance().creators[name] = [](const nlohmann::json &params)
            { return std::shared_ptr<N>(new N(params)); };
            // std::cout << "Registering " << name << " with " << typeid(N).name() << std::endl;
        }
    };

    static Factory &Instance()
    {
        static Factory instance;
        return instance;
    }
    std::shared_ptr<T> Create(const nlohmann::json &params, const std::string &type_arg = "type")
    {
        auto func = creators.find(params.at(type_arg).get<std::string>());
        if (func != creators.end())
        {
            return func->second(params);
        }
        return nullptr;
    };

    int GetNumCreators() const { return creators.size(); }

  private:
    Factory() {}
    ~Factory() {}
    Factory(const Factory &) = delete;
    Factory &operator=(const Factory &) = delete;

  private:
    std::map<std::string, std::function<std::shared_ptr<T>(const nlohmann::json &)>> creators;
};